﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Timers;
using PSE.Hosting.ServiceHoster.Business;
using PSE.Hosting.ServiceHoster.Configuration;
using PSE.Hosting.ServiceHoster.Logging;

namespace PSE.Hosting.ServiceHoster.Business
{
    public delegate void AssemblyEventHandler(object source, AssemblyEventArgs eventArgs);

    internal class AssemblyState : IDisposable
    {
        #region Private Fields

        private string _packageName;
        private string _codeBase;
        private string _shadowCodeBase;
        private bool _isValid;
        private PackageServicesLoader _proxy;
        private PSE.Framework.IO.FileSystemWatcher _fileSystemWatcher;
        public event AssemblyEventHandler AssemblyCreated;
        public event AssemblyEventHandler AssemblyDeleted;
        public event AssemblyEventHandler AssemblyChanged;
        protected bool _disposed = false;
        private DomainController _domainController;

        #endregion

        #region Constructors

        public AssemblyState(string packageName, string codeBase)
        {
            if (!File.Exists(codeBase))
                throw new FileNotFoundException(codeBase);

            _packageName = packageName;
            _codeBase = codeBase;
            _isValid = false;

            _fileSystemWatcher = new PSE.Framework.IO.FileSystemWatcher(Path.GetDirectoryName(_codeBase), Path.GetFileName(_codeBase) + "*");
            
            _fileSystemWatcher.Created += new FileSystemEventHandler(_fileSystemWatcher_Created);
            _fileSystemWatcher.Changed += new FileSystemEventHandler(_fileSystemWatcher_Changed);
            _fileSystemWatcher.Deleted += new FileSystemEventHandler(_fileSystemWatcher_Deleted);
            
            _fileSystemWatcher.Start();

            _domainController = DomainController.GetInstance();

            _domainController.MergeConfigurationFile(_codeBase + ".config");
        }
        
        ~AssemblyState()
        {
            Dispose(false);
        }

        #endregion

        #region Properties

        public string FileName
        {
            get { return Path.GetFileName(_codeBase); }
        }

        public string AssemblyFullName
        {
            get
            {
                if (_proxy != null)
                {
                    return _proxy.GetAssemblyName();
                }
                else
                {
                    return string.Empty;
                }
            }
        }

        public string CodeBase
        {
            get { return _codeBase; }
        }

        public bool IsValid
        {
            get { return _isValid; }
            set { _isValid = value; }
        }

        #endregion

        #region Public Methods

        public Dictionary<string, ServiceData> GetServices()
        {
            return GetProxyInstance().GetServices();
        }

        public Dictionary<string, Exception> StartAll()
        {
            Dictionary<string, Exception> exceptions = null;

            RefreshSections();

            exceptions = GetProxyInstance().StartAll();

            return exceptions;
        }

        public void Start(string serviceName)
        {
            RefreshSections();

            GetProxyInstance().Start(serviceName);
        }

        public void StopAll()
        {
            GetProxyInstance().StopAll();
        }

        public void Stop(string serviceName)
        {
            GetProxyInstance().Stop(serviceName);
        }

        public void RefreshSections()
        {
            GetProxyInstance().RefreshSections();
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {

                }

                if (_proxy != null)
                {
                    _proxy.StopAll();
                    _proxy = null;
                }

                if (_fileSystemWatcher != null)
                {
                    _fileSystemWatcher.Dispose();
                    _fileSystemWatcher = null;
                }

                _domainController = null;
                _disposed = true;
            }
        }

        #endregion

        #region Private Methods

        private void _fileSystemWatcher_Created(object sender, FileSystemEventArgs e)
        {
            if (Path.GetExtension(e.FullPath) == ".dll" || e.FullPath.EndsWith(".dll.config"))
            {
                string codeBase;

                if (e.FullPath.EndsWith(".dll.config"))
                {
                    // Se for um config, captura o nome do assembly e verifica se ele já foi criado
                    codeBase = Path.Combine(Path.GetDirectoryName(e.FullPath), Path.GetFileNameWithoutExtension(e.FullPath));

                    if (!File.Exists(codeBase))
                        return;
                }
                else
                {
                    // Se for um assembly, verifica se o config já foi criado
                    codeBase = e.FullPath;

                    if (!File.Exists(codeBase + ".config"))
                        return;
                }

                AssemblyEventArgs eventArgs = new AssemblyEventArgs();
                eventArgs.CodeBase = codeBase;
                eventArgs.PackageName = _packageName;

                if (AssemblyCreated != null)
                    AssemblyCreated(this, eventArgs);
            }
        }

        private void _fileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
        {
            if (Path.GetExtension(e.FullPath) == ".dll" || e.FullPath.EndsWith(".dll.config"))
            {
                string codeBase;

                if (e.FullPath.EndsWith(".dll.config"))
                {
                    // Se for um config, captura o nome do assembly e verifica se ele já foi criado
                    codeBase = Path.Combine(Path.GetDirectoryName(e.FullPath), Path.GetFileNameWithoutExtension(e.FullPath));

                    if (!File.Exists(codeBase))
                        return;
                }
                else
                {
                    // Se for um assembly, verifica se o config já foi criado
                    codeBase = e.FullPath;

                    if (!File.Exists(codeBase + ".config"))
                        return;
                }

                AssemblyEventArgs eventArgs = new AssemblyEventArgs();
                eventArgs.CodeBase = codeBase;
                eventArgs.PackageName = _packageName;

                if (AssemblyChanged != null)
                    AssemblyChanged(this, eventArgs);
            }
        }

        private void _fileSystemWatcher_Deleted(object sender, FileSystemEventArgs e)
        {
            if (Path.GetExtension(e.FullPath) == ".dll" || e.FullPath.EndsWith(".dll.config"))
            {
                string codeBase;

                if (e.FullPath.EndsWith(".dll.config"))
                {
                    codeBase = Path.Combine(Path.GetDirectoryName(e.FullPath), Path.GetFileNameWithoutExtension(e.FullPath));
                }
                else
                {
                    codeBase = e.FullPath;
                }

                AssemblyEventArgs eventArgs = new AssemblyEventArgs();
                eventArgs.CodeBase = codeBase;
                eventArgs.PackageName = _packageName;

                if (AssemblyDeleted != null)
                    AssemblyDeleted(this, eventArgs);
            }
        }

        private PackageServicesLoader GetProxyInstance()
        {
            if (_proxy == null)
            {
                InitializeServiceInstance();
            }

            return _proxy;
        }

        internal void InitializeServiceInstance()
        {
            GenerateShadownFile();

            SetFolderListCache();

            _proxy = (PackageServicesLoader)_domainController.ServiceDomain.CreateInstanceFromAndUnwrap(
                Assembly.GetExecutingAssembly().CodeBase, typeof(PackageServicesLoader).FullName);

            _proxy.LoadServicesAssembly(_shadowCodeBase);
        }

        private void GenerateShadownFile()
        {
            _domainController.ServiceDomain.SetData("ShadowCodeBaseDirectory", _domainController.ShadowBaseDirectory);

            int count = Directory.GetFiles(_domainController.ShadowBaseDirectory, Path.GetFileNameWithoutExtension(_codeBase) + "*.dll").Length + 1;

            _shadowCodeBase = string.Format(@"{0}\{1}{2}{3}", _domainController.ShadowBaseDirectory, Path.GetFileNameWithoutExtension(_codeBase), count, ".dll");

            File.Copy(_codeBase, _shadowCodeBase);

            File.SetAttributes(_shadowCodeBase, FileAttributes.Normal);

            if (File.Exists(_codeBase + ".config"))
            {
                File.Copy(_codeBase + ".config", _shadowCodeBase + ".config");

                File.SetAttributes(_shadowCodeBase + ".config", FileAttributes.Normal);
            }
        }

        private void SetFolderListCache()
        {
            List<string> folderList = (List<string>)_domainController.ServiceDomain.GetData("FolderList");

            if (folderList == null)
            {
                folderList = new List<string>();
            }

            if (!folderList.Contains(Path.GetDirectoryName(_codeBase)))
                folderList.Add(Path.GetDirectoryName(_codeBase));

            _domainController.ServiceDomain.SetData("FolderList", folderList);
        }

        #endregion
    }
}
